/*
  Time-stamp: <2009-11-12 13:23:08 xinhaoyuan>

  File: kpipe.c
  
  This file provides routines to create, read and write with kernel
  level queue, which can has unlimited of memory.
  
  A struct of Kernel Pipe, kpipe for short, is composed from two
  component, the control block, and the access area. Control block
  contains meta information of the queue, including read/write
  pointer, resource limit, and so on. And the access area only occurs
  when writing / reading is performed.

  The kenrel pipe is used for kernel debugging/logging, events
  handling/dispatching.
  
  For security, the control block is a page that can be accessed by
  only kernel.

  For additional, the file also provide some function for printing
  with format into a kpipe.

*/

#define _EKOS_KERNEL_C_

#include <kernel/kernel.h>

/* compiling time check for size of struct kpipe_t */
static char __kpipe_size1[sizeof(struct kpipe_t) == PAGE_SIZE * 2 ? 1 : -1];
static char __kpipe_size2[sizeof(struct kpipe_buf_t) == PAGE_SIZE ? 1 : -1];

int
kpipe_open(struct kpipe_t *kpipe)
{
	 mutex_init(&kpipe->lock);
	 kpipe->rptr = kpipe->wptr = 0;

	 physaddr_t addr = PTE_ADDR(vpt[PAGE_NUM(&kpipe->pbuf)]);
	 kpipe->head = kpipe->tail = addr;
	 frame_add_ref(addr, 2);

	 kpipe->pages_count = 1;

	 return 0;
}

int
kpipe_write_unsafe(struct kpipe_t *kpipe, const void *buf, int n)
{
	 int result;
	 
	 result = KPIPE_BUFSIZE - kpipe->wptr;
	 if (result > n)
		  result = n;

	 vpt[PAGE_NUM(&kpipe->pbuf)] = kpipe->tail | PTE_W | PTE_P;
	 tlbflush();

	 memmove(kpipe->pbuf.buf + kpipe->wptr, buf, result);
	 kpipe->wptr += result;
	 
	 if (kpipe->wptr == KPIPE_BUFSIZE)
	 {
		  if (frame_alloc(&kpipe->pbuf.next) == 0)
		  {
			   kpipe->tail = kpipe->pbuf.next;
			   frame_add_ref(kpipe->pbuf.next, 2);

			   frame_dec_ref(PTE_ADDR(vpt[PAGE_NUM(&kpipe->pbuf)]));

			   kpipe->wptr = 0;

#ifdef K_KPIPE_DEBUG
			   cprintf("[kpipe_write] allocating new page\n");
#endif

			   ++kpipe->pages_count;
		  }
		  else
		  {
#ifdef K_KPIPE_DEBUG
			   cprintf("[kpipe_write] trying allocating new page, resource not enough\n");
#endif
		  }
		  // cprintf("\nkpipe->page_count = %d\n", kpipe->pages_count);
	 }

#ifdef K_KPIPE_DEBUG
	 cprintf("[kpipe_write] write %d bytes, wptr = %d\n", result, kpipe->wptr);
#endif

	 return result;
}

int
kpipe_read_unsafe(struct kpipe_t *kpipe, void *buf, int n)
{
	 int result;

	 if (kpipe->head == kpipe->tail)
		  result = kpipe->wptr - kpipe->rptr;
	 else result = KPIPE_BUFSIZE - kpipe->rptr;
	 
	 if (result > n)
		  result = n;


	 vpt[PAGE_NUM(&kpipe->pbuf)] = kpipe->tail | PTE_W | PTE_P;
	 tlbflush();

	 memmove(buf, kpipe->pbuf.buf + kpipe->rptr, result);
	 kpipe->rptr += result;

	 if (kpipe->rptr == KPIPE_BUFSIZE)
	 {
		  kpipe->head = kpipe->pbuf.next;
		  frame_dec_ref(kpipe->pbuf.next);
		  frame_dec_ref(PTE_ADDR(vpt[PAGE_NUM(&kpipe->pbuf)]));

		  kpipe->rptr = 0;

		  --kpipe->pages_count;
		  // cprintf("\nkpipe->page_count = %d\n", kpipe->pages_count);
	 }

	 clear_tmp();
	 // tlbflush();

	 return result;	 
}

int
kpipe_read(struct kpipe_t *kpipe, void *buf, int n)
{
	 mutex_lock(&kpipe->lock);
	 int result = kpipe_read_unsafe(kpipe, buf, n);
	 mutex_unlock(&kpipe->lock);
}

int
kpipe_write(struct kpipe_t *kpipe, const void *buf, int n)
{
	 mutex_lock(&kpipe->lock);
	 int result = kpipe_write_unsafe(kpipe, buf, n);
	 mutex_unlock(&kpipe->lock);
}


/* put function */
struct kpputch_buf_t
{
	 struct kpipe_t *kpipe;
	 int size;
	 char buf[KPPUTCH_BUFSIZE];
};

inline static void
flush_kpputch_buf(struct kpputch_buf_t *pbuf)
{
	 int i, j;
	 j = 0;
	 
	 while (pbuf->size != j)
	 {
		  if ((i = kpipe_write(pbuf->kpipe, pbuf->buf + j, pbuf->size - j)) == 0) break;
		  j += i;
	 }
	 
	 pbuf->size = 0;
}

static void
kp_putch(int ch, void *data)
{
	 struct kpputch_buf_t *pbuf = (struct kpputch_buf_t *)data;
	 pbuf->buf[pbuf->size ++] = ch;
	 
	 if (pbuf->size == KPPUTCH_BUFSIZE)
	 {
		  flush_kpputch_buf(pbuf);
	 }
}

void
vkpprintf(struct kpipe_t *kpipe, const char *fmt, va_list ap)
{
	 struct kpputch_buf_t b;
	 
	 b.kpipe = kpipe;
	 b.size = 0;

     // print the string to the buffer
     vprintfmt((void*)kp_putch, &b, fmt, ap);
	 flush_kpputch_buf(&b);
}

void
kpprintf(struct kpipe_t *kpipe, const char *fmt, ...)
{
     va_list ap;

     va_start(ap, fmt);
     vkpprintf(kpipe, fmt, ap);
     va_end(ap);
}
